package vg.modules.notepad;
 
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.Scanner;

import javax.swing.*;

import vg.modules.notepad.components.ConnectionStatistic;
import vg.modules.notepad.components.Desktop;
import vg.modules.notepad.components.connectorComponent.Connector;
import vg.modules.notepad.components.textComponent.ITextComponent;
import vg.modules.notepad.components.textComponent.TextComponent;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.UIEventCloseAIF;
import vg.services.plugin_manager.event_and_request.event.UIEventCreateNewConnection;
import vg.services.plugin_manager.event_and_request.event.UIEventDeleteConnection;
import vg.services.plugin_manager.event_and_request.request.UIRequestGoToInAIF;
import vg.services.plugin_manager.realization.PluginParameter;
import vg.services.user_interface_manager.additional_swing_components.SimpleStatusBar;

/**
 * This is a class for reading of the documents,
 * has the ability to copy, cut, paste & select all text.
 * @author tzolotuhin
 */
public class Notepad extends JFrame{
	private static final long serialVersionUID = 1L;
	// Main data
	private boolean init = false; // for late initialization 
	private PluginParameter param;
	// Toolbar
	private JToolBar toolBar;
	// Status bar
	private SimpleStatusBar statusBar;
	// Buttons
	private JButton openButton;
	private JButton searchButton, connectionButton, connectionStatisticButton;
	private JTextField searchField;
	// Panels
	private Desktop desktop;
	private Connector connector;
	private ConnectionStatistic connectionStatistic;
	// Mutex
	private final Object theMutexObject;
	// Defines
	private final String DEF_NOTEPAD_FOLDER = "NotepadPluginSave";
	//-------------------------------------------------------------------------
	private final static String DEF_WINDOWS_SIZE_X = "NotepadPlugin_WindowSizeX";
	private final static String DEF_WINDOWS_SIZE_Y = "NotepadPlugin_WindowSizeY";
	private final static String DEF_WINDOWS_POS_X = "NotepadPlugin_WindowPosX";
	private final static String DEF_WINDOWS_POS_Y = "NotepadPlugin_WindowPosY";
	private Integer windowSizeX, windowSizeY;
	private Integer windowPosX, windowPosY;
	//-------------------------------------------------------------------------
	/**
	 * Constructor of Notepad.
	 */
	public Notepad(final PluginParameter param){ 
		// save input parameter
		this.param = param;
		// init mutex
		this.theMutexObject = new Object();
	}
	/**
	 * This method opens notepad.
	 */
	public void open() {
		synchronized (theMutexObject) {
			if(!init) {
				if(SwingUtilities.isEventDispatchThread()) {
					init();
				} else {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							synchronized (theMutexObject) {
								init();
							}
						};
					});
				}
			}
			// open notepad
			if(SwingUtilities.isEventDispatchThread()) {
				this.setVisible(true);
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (theMutexObject) {
							Notepad.this.setVisible(true);
						}
					};
				});
			}
		}
	}
	/**
	 * This method dispose the notepad.
	 */
	public void dispose() {
		synchronized (theMutexObject) {
			if(SwingUtilities.isEventDispatchThread()) {
				super.dispose();
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (theMutexObject) {
							Notepad.super.dispose();
						}
					};
				});
			}			
		}
	}
	/**
	 * This method closes existing file with additional information.
	 * @param aif - existing file with additional information.
	 */
	public void closeAIF(final UIEventCloseAIF event) {
		synchronized (theMutexObject) {
			synchronized (theMutexObject) {
				if(SwingUtilities.isEventDispatchThread()) {
					connectionStatistic.removeConnectionByFileId(event.getFileId());
				} else {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							synchronized (theMutexObject) {
								connectionStatistic.removeConnectionByFileId(event.getFileId());
							}
						};
					});
				}			
			}
		}
	}
	public void goToAIF(final UIRequestGoToInAIF request) {
		if(request == null) {return;}
		synchronized (theMutexObject) {
			UIEventCreateNewConnection connection = null;
			for(UIEventCreateNewConnection buf : this.connectionStatistic.getAllConnections()) {
				if(buf.getConnectionId() == request.getConnectId()) {
					connection = buf;
					break;
				}
			}
			// work with Swing ui
			if(connection != null) {
				if(SwingUtilities.isEventDispatchThread()) {
					ITextComponent tc  = this.desktop.getComponent(connection.getFileId());
					if(tc != null) {
						tc.selectAnchor(request.getAnchorValue());
					}
					this.desktop.selectTabAt(connection.getFileId());
				} else {
					final int fileId = connection.getFileId();
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							synchronized (theMutexObject) {
								ITextComponent tc  = desktop.getComponent(fileId);
								if(tc != null) {
									tc.selectAnchor(request.getAnchorValue());
								}
								desktop.selectTabAt(fileId);															
							}
						}
					});
				}
			}
		}
	}
	/**
	 * This method adds a connection to pool of existing connections.
	 * @param connection - existing connection.
	 */
	public void addConnection(final UIEventCreateNewConnection connection) {
		if(connection == null) {return;}
		synchronized (theMutexObject) {
			if(SwingUtilities.isEventDispatchThread()) {
				connectionStatistic.addConnection(connection);
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (theMutexObject) {
							connectionStatistic.addConnection(connection);
						}
					};
				});
			}
		}
	}
	/**
	 * This method deletes a connection from pool of existing connections.
	 * @param connection - existing connection.
	 */
	public void deleteConnection(final UIEventDeleteConnection event) {
		if(event == null) {return;}
		synchronized (theMutexObject) {
			if(SwingUtilities.isEventDispatchThread()) {
				connectionStatistic.removeConnection(event.getConnectId());
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (theMutexObject) {
							connectionStatistic.removeConnection(event.getConnectId());
						}
					};
				});
			}
		}
	}
	/**
	 * This method updates user interface theme.
	 */
	public void updateUITheme() {
		synchronized (theMutexObject) {
			if(init) {
				if(SwingUtilities.isEventDispatchThread()) {
					SwingUtilities.updateComponentTreeUI(this);
					desktop.updateUITheme();
					connectionStatistic.updateUITheme();
					connector.updateUIThemes();
				} else {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							synchronized (theMutexObject) {
								SwingUtilities.updateComponentTreeUI(Notepad.this);
								desktop.updateUITheme();
								connectionStatistic.updateUITheme();
								connector.updateUIThemes();
							}
						}
					});
				}
			}			
		}
	}
	/**
	 * This method resets the notepad.
	 * Closes all tabs, removes all connections.
	 */
	public void reset() {
		synchronized (theMutexObject) {
			if(!init) {
				if(SwingUtilities.isEventDispatchThread()) {
					init();
				} else {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							synchronized (theMutexObject) {
								init();
							}
						};
					});
				}
			}
			if(SwingUtilities.isEventDispatchThread()) {
				desktop.closeAllTab();
				connectionStatistic.removeAllConnections();
			} else {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						synchronized (theMutexObject) {
							desktop.closeAllTab();
							connectionStatistic.removeAllConnections();
						}
					}
				});
			}
		}
	}
	/**
	 * This method saves the project. Saves all connections and tabs.
	 */
	public void saveProject(String path) {
		synchronized (theMutexObject) {
			if(!init) return;
			// create folder for saving of different parts of notepad
			File outputFolder = new File(path + File.separator + DEF_NOTEPAD_FOLDER);
			if(outputFolder.exists()) {
				deleteDirectory(outputFolder);
			}
			if(!outputFolder.mkdir()) {
				MainManager.windowMessage.errorMessage("Can't save notepad's files and current connections.", "Save notepad's files");
				return;
			}
			List<UIEventCreateNewConnection>allConnections = this.connectionStatistic.getAllConnections();
			for(UIEventCreateNewConnection buf : allConnections) {
				File dir = new File(outputFolder.getAbsolutePath() + File.separator + buf.getFileId());
				if(!dir.mkdir()) {
					MainManager.windowMessage.errorMessage("Can't save notepad's files.", "Save notepad's files");
					return;					
				}
				try {
					File oldFile = new File(this.desktop.getFullFileNameAt(buf.getFileId()));
					File newFile = new File(dir.getAbsolutePath() + File.separator + oldFile.getName());
					//we don't want have problems with coping file into itself
					if (oldFile.compareTo(newFile) == 0) {
						continue;
					}
					FileChannel originalGraphFile = new FileInputStream(oldFile).getChannel();
					FileChannel copyFile = new FileOutputStream(newFile).getChannel();
					try {
						originalGraphFile.transferTo(0, originalGraphFile.size(), copyFile);
					} catch (IOException e1) {
						throw e1;
					} finally {
						if (originalGraphFile != null) 
							originalGraphFile.close();
						if (copyFile != null)
							copyFile.close();
					}
				} catch (IOException e1) {
					MainManager.logger.printException(e1);
				}			
			}
			// save connections
			FileOutputStream fos = null;
			try {
				fos = new FileOutputStream(new File(outputFolder + File.separator + "connections.txt"));
				DataOutputStream dos = new DataOutputStream(fos);
				for(UIEventCreateNewConnection buf : allConnections) {
					dos.writeBytes("Connection #" + buf.getConnectionId());dos.writeBytes("\r\n");
					dos.writeBytes(buf.getConnectionName());dos.writeBytes("\r\n");
					dos.writeBytes(Integer.valueOf(buf.getFileId()).toString());dos.writeBytes("\r\n");
					dos.writeBytes(Integer.valueOf(buf.getGraphId()).toString());dos.writeBytes("\r\n");
					dos.writeBytes(buf.getGraphName());dos.writeBytes("\r\n");
					dos.writeBytes(buf.getAnchor());dos.writeBytes("\r\n");
				}
			} catch (IOException ex) {
				MainManager.windowMessage.errorMessage("Can't save connections.", "Save notepad's files");
			} finally {
				if(fos != null) {
					try {
						fos.close();
					} catch (Exception ex) {
						
					}
				}
			}
			
		}
	}
	/**
	 * This method loads all connections and tabs.
	 */
	public void loadProject(final String path) {
		if(!SwingUtilities.isEventDispatchThread()) {
			SwingUtilities.invokeLater(new Runnable() {
				public void run() {
					loadProject(path);
				}
			});
			return;
		}
		synchronized (theMutexObject) {
			if(!init) {
				init();
			}
			File inputFolder = new File(path + File.separator + DEF_NOTEPAD_FOLDER);
			if(inputFolder.exists() && inputFolder.isDirectory()) {
				// load notepad's files
				String[] dirs = inputFolder.list();
				for(int i = 0; i < dirs.length; i++) {
					File innerDir = new File(inputFolder.getAbsolutePath() + File.separator + dirs[i]);
					if(innerDir.exists()) {
						String[] list = innerDir.list();
						if(list != null) {
							if(list.length >= 1) {
								final File workFile = new File(innerDir.getAbsolutePath() + File.separator + list[0]);
								ITextComponent tc = open(workFile);
								if(tc != null) {
									try {
										Integer tabId = new Integer(dirs[i]);
										this.desktop.addTab(tabId, workFile.getName(), workFile.getAbsolutePath(), tc);
									} catch (Throwable ex) {
										
									}
								}
							}
						}
					}
				}
				// load connections
				File connectionsFile = new File(inputFolder.getAbsolutePath() + File.separator + "connections.txt");
				if (connectionsFile.exists()) {
					FileInputStream fis = null;
					try {
						fis = new FileInputStream(connectionsFile);
						Scanner scanner = new Scanner(fis);
						while(scanner.hasNext()) {
							scanner.nextLine();
							String connectionName = scanner.nextLine();
							int fileId = scanner.nextInt();
							int graphId = scanner.nextInt();scanner.nextLine();
							String graphName = scanner.nextLine();
							String anchor = scanner.nextLine();
							this.connector.addConnection(connectionName, anchor, graphId, graphName, fileId);
						}
					} catch (Throwable ex) {
						MainManager.windowMessage.errorMessage("Can't load connections.", "Load notepad's files");
					} finally {
						if(fis != null) {
							try {
								fis.close();
							} catch (Exception ex) {
								
							}
						}
					}					
				}
			}
		}
	}
	///////////////////////////////////////////////////////////////////////////
	// PRIVATE METHODS
	///////////////////////////////////////////////////////////////////////////
	/**
	 * Initializes all swing(and other) graphical components.
	 */
	private void init() {
		this.init = true;
		//set the title for Notepad and set the size for it.
		loadWindowOptions();
		this.setTitle("Notepad");
		this.setSize(this.windowSizeX, this.windowSizeY);
		this.setLocation(this.windowPosX, this.windowPosY);
		this.setDefaultCloseOperation(HIDE_ON_CLOSE);
		this.setLayout(new BorderLayout());
		this.addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				super.componentResized(e);
			    Component c = (Component)e.getSource();
		        Integer sizeX = c.getSize().width;
		        Integer sizeY = c.getSize().height;
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, sizeX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, sizeY.toString());
			}
			public void componentMoved(ComponentEvent e) {
				super.componentMoved(e);
			    Component c = (Component)e.getSource();
		        Integer posX = c.getLocation().x;
		        Integer posY = c.getLocation().y;
		        MainManager.config.setProperty(DEF_WINDOWS_POS_X, posX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_POS_Y, posY.toString());
			}
		});
		// create components
		this.connector = new Connector(param, this);
		this.connectionStatistic = new ConnectionStatistic(this, param);
		
		this.toolBar = new JToolBar("Tool Bar");
		this.toolBar.setLayout(new GridBagLayout());
		this.toolBar.setFloatable(false);
		
		this.desktop = new Desktop(param);
		
		this.statusBar = new SimpleStatusBar();
		
		this.openButton  = new JButton(new ImageIcon("./data/resources/textures/notepad/openFile.png"));
		this.openButton.setToolTipText("Open file");
		
		this.searchButton = new JButton(new ImageIcon("./data/resources/textures/notepad/search.png"));
		this.searchButton.setToolTipText("Search");
		this.searchButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				synchronized (theMutexObject) {
					find();
				}
			}
		});
		this.searchField = new JTextField();
		this.searchField.setColumns(15);
		this.searchField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				synchronized (theMutexObject) {
					find();
				}
			}
		});
		
		this.connectionButton = new JButton(new ImageIcon("./data/resources/textures/notepad/addConnection.png"));
		this.connectionButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int fileId = desktop.getCurrentId();
				for(UIEventCreateNewConnection buf : connectionStatistic.getAllConnections()) {
					if(buf.getFileId() == fileId) {
						connector.replace(buf.getConnectionId(), buf.getConnectionName(), fileId, buf.getGraphId(), buf.getAnchor());
						return;
					}
				}
				String shortName = desktop.getCurrentShortFileName();
				connector.open(fileId, shortName);
			}
		});
		
		this.connectionStatisticButton = new JButton(new ImageIcon("./data/resources/textures/notepad/editConnection.png"));
		this.connectionStatisticButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				connectionStatistic.open();
			}
		});
		
		this.openButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent ae){
				synchronized (theMutexObject) {
					JFileChooser jfc = new JFileChooser(".");
					int returnVal = jfc.showOpenDialog(Notepad.this); //to show JFileChooser
					if(returnVal == JFileChooser.APPROVE_OPTION){
						ITextComponent tc = open(jfc.getSelectedFile());
						desktop.addTab(jfc.getSelectedFile().getName(), jfc.getSelectedFile().getAbsolutePath(), tc);
					}
				}
			}
		});
		// pack ui
		this.add(this.toolBar, BorderLayout.NORTH);
		this.add(this.desktop.getView(), BorderLayout.CENTER);
		this.add(this.statusBar, BorderLayout.SOUTH);
		// pack toolbar
		GridBagConstraints gbc = null;
		gbc = new GridBagConstraints(0,0, 1,1, 0,0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0,0,0,0), 0,0);		
		this.toolBar.add(this.openButton, gbc);
		
		gbc = new GridBagConstraints(1,0, 1,1, 0,0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0,0,0,0), 0,0);		
		this.toolBar.add(this.connectionButton, gbc);
		
		gbc = new GridBagConstraints(2,0, 1,1, 0,0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0,0,0,0), 0,0);		
		this.toolBar.add(this.connectionStatisticButton, gbc);

		gbc = new GridBagConstraints(3,0, 1,1, 1,0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0,5,0,0), 0,0);
		this.toolBar.add(this.searchField, gbc);
		
		gbc = new GridBagConstraints(4,0, 1,1, 0,0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0,0,0,5), 0,0);		
		this.toolBar.add(this.searchButton, gbc);
		// good
		this.setVisible(false);
	}
	private ITextComponent open(File input) {
		//to erase any text in the text area before adding new text
		ITextComponent textComponent = new TextComponent();
		StringBuffer str = new StringBuffer(1024*4);
		Reader in = null;
		try{
			//to read the selected file 
			in = new FileReader(input);
			//100000 is the max. char can be written in the text area
			char[] buff = new char[100000];
			int nch;
			while((nch = in.read(buff, 0, buff.length)) != -1) {
				str.append(new String(buff, 0, nch));
			}
			textComponent.setText(str.toString());
			return(textComponent);
		} catch(Exception ex){
			MainManager.logger.printException(ex);
		} finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException ex) {
					
				}
			}
		}
		return(null);
	}
	/**
	 * This method finds substring in current text document.
	 */
	private void find() {
		String whatFind = this.searchField.getText();
		ITextComponent textComponent = this.desktop.getCurrentTextComponent();
		if(textComponent != null) {
			textComponent.find(whatFind);
		}
	}
	/**
	 * This method deletes non-empty directory.
	 */
	public static boolean deleteDirectory(File path) {
		if (path.exists()) {
			File[] files = path.listFiles();
			for (int i = 0; i < files.length; i++) {
				if (files[i].isDirectory()) {
					deleteDirectory(files[i]);
				} else {
					files[i].delete();
				}
			}
		}
		return (path.delete());
	}

	/**
	 * This method loads window options.
	 */
	private void loadWindowOptions() {
		String wSizeStrX = MainManager.config.getProperty(DEF_WINDOWS_SIZE_X);
		String wSizeStrY = MainManager.config.getProperty(DEF_WINDOWS_SIZE_Y);
		String wPosStrX = MainManager.config.getProperty(DEF_WINDOWS_POS_X);
		String wPosStrY = MainManager.config.getProperty(DEF_WINDOWS_POS_Y);
		//set size X(width of window)
		if(wSizeStrX == null) {
			this.windowSizeX = 800;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
		} else {
			try {
				this.windowSizeX = new Integer(wSizeStrX);
			} catch(NumberFormatException ex) {
				this.windowSizeX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
			}
		}
		//set size Y(height of window)
		if(wSizeStrY == null) {
			this.windowSizeY = 600;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());			
		} else {
			try {
				this.windowSizeY = new Integer(wSizeStrY);
			} catch(NumberFormatException ex) {
				this.windowSizeY = 600;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());
			}			
		}
		//set position X
		if(wPosStrX == null) {
			this.windowPosX = 800;
			MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
		} else {
			try {
				this.windowPosX = new Integer(wPosStrX);
			} catch(NumberFormatException ex) {
				this.windowPosX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
			}
		}
		//set position Y
		if(wPosStrY == null) {
			this.windowPosY = 0;
			MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
		} else {
			try {
				this.windowPosY = new Integer(wPosStrY);
			} catch(NumberFormatException ex) {
				this.windowPosY = 0;
				MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
			}
		}
	} 
}			